using System;
using System.CodeDom;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;



namespace Comdiv
{
#if MONORAIL
    internal static partial class MainExtensionSet
    {
#else
    public static partial class MainExtensionSet{
#endif
        public static string toSygnatureString(this MethodInfo info)
        {
            //Contract.Requires(null!=info);
            var b = new StringBuilder();
            if (info.IsPublic) b.Append("public ");
            if (info.IsFamily) b.Append("protected ");
            if (info.IsAssembly) b.Append("internal ");
            if (info.IsStatic) b.Append("static ");
            if (info.IsVirtual) b.Append("virtual ");
            if (info.ReturnType == null || info.ReturnType.Equals(typeof(void))) b.Append("void ");
            else b.Append(info.ReturnType.toSygnatureString() + " ");
            b.Append(info.Name);
            if (info.IsGenericMethodDefinition) b.Append((string)_getGenericString(info.GetGenericArguments()));
            b.Append("(");
            if (info.hasAttribute<ExtensionAttribute>()) b.Append("this ");
            var parameters = new List<string>();
            foreach (var parameter in info.GetParameters())
            {
                parameters.Add("{2}{0} {1}".fmt(
                                   parameter.ParameterType.toSygnatureString(),
                                   parameter.Name,
                                   parameter.GetCustomAttributes(typeof(ParamArrayAttribute), false).yes()
                                       ? "params "
                                       : ""
                                   ));
            }
            b.Append(String.Join(", ", parameters.ToArray()));
            b.Append(")");
            return b.ToString();
        }

        public static string toSygnatureString(this Type type)
        {

            // Contract.Assert(type!=null);
            var result = type.Name;
            if (type.IsGenericType)
            {
                var args = type.GetGenericArguments();
                //Contract.Assume(args != null);
                result += _getGenericString(args);
            }
            return result;
        }

        private static string _getGenericString(IEnumerable<Type> args)
        {
            //Contract.Requires(args != null);
            var genstr = "<";
            foreach (var argument in args)
            {
                genstr += argument.toSygnatureString();
            }
            genstr += ">";
            return genstr;
        }
        public static PropertyInfo resolveProperty(this Type type, string name)
        {
            var result = type.getPropertyInfo(name);
            if (null == result && type.IsInterface)
            {
                foreach (var intType in type.GetInterfaces())
                {
                    result = intType.resolveProperty(name);
                    if (null != result) break;
                }
            }

            return result;
        }

        public static Type toType(this string str)
        {
            if (str.noContent()) return null;
            if (str.EndsWith(".dll")) str = str.Substring(0, str.Length - 4);
            var result = Type.GetType(str);
            if (null == result) throw new NullReferenceException(str + " maps to non-correct or unavailable type");
            return result;
        }
        public static T getFirstAttribute<T>(this Type type)
           where T : Attribute
        {
            return (T)type.GetCustomAttributes(typeof(T), true).FirstOrDefault();
        }

        public static T getFirstAttribute<T>(this MemberInfo member)
            
            where T : Attribute
        {
            return (T)member.GetCustomAttributes(typeof(T), true).FirstOrDefault();
        }

        public static object create(this Type type)
        {
            return type.create<object>();
        }

        public static T create<T>(this Type type)
        {
            return type.GetConstructor(Type.EmptyTypes).Invoke(null).to<T>();
        }

        public static T create<T>(this Type t, params object[] parameters)
        {
            var types = Enumerable.Select(parameters, o => null == o
                         ? typeof(object)
                         : o.GetType()
                ).ToArray();

            return (T)t.GetConstructor(types).Invoke(parameters);
        }

        public static T create<T>(this Type t, Type[] types, params object[] parameters)
        {
            return (T)t.GetConstructor(types).Invoke(parameters);
        }

        public static bool hasAttribute<T>(this MemberInfo info) where T:Attribute
        {
            if (info == null) throw new ArgumentNullException("info");

            return info.GetCustomAttributes(typeof(T), true).yes();
        }

        public static bool hasAttribute<T>(this MemberInfo info, Func<T, bool> attributeFilter) where T : Attribute
        {
            if (info == null) throw new ArgumentNullException("info");

            return info.GetCustomAttributes(typeof(T), true).Cast<T>().Where(attributeFilter).yes();
        }

        public static bool hasAttribute(this MemberInfo info, Type attributeType)
        {
            if (info == null) throw new ArgumentNullException("info");
            if (attributeType == null) throw new ArgumentNullException("attributeType");
            return info.GetCustomAttributes(attributeType, true).yes();
        }


        
        public static bool hasProperty(this Type type, string name)
        {
            return null != type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public);
        }

        public static PropertyInfo getPropertyInfo(this object target, string name, BindingFlags flags)
        {
            if (null == target) return null;
            return target.GetType().GetProperty(name, flags);
        }
        public static PropertyInfo getPropertyInfo(this object target, string name)
        {
            if (null == target) return null;
            return target.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase);
        }

        public static T getPropertySafe<T>(this object target, string name)
        {
            return target.getPropertySafe(name, default(T));
        }

        public static T getPropertySafe<T>(this object target, string name, T def)
        {
            if (null == target) return def;
            var prop = target.getPropertyInfo(name);
            if (null == prop) return def;
            return prop.GetValue(target, null).to(true, def);
        }

        public static T setPropertySafe<T>(this T target, string name, object value)
        {
            if (null == target) return target;

            var pi = target.getPropertyInfo(name);
            if (null == pi) return target;
            try
            {
                var ttype = pi.PropertyType;
                if (null != value || !ttype.IsAssignableFrom(value.GetType()))
                {
                    value = to(value, ttype);


                }
                pi.SetValue(target, value, null);
            }
            catch { }
            return target;
        }

        public static T getProperty<T>(this object target,string name)
        {
            return getProperty(target, name).to<T>();
        }

        public static object getProperty(this object target, string name)
        {
            if (null == target)
                throw new InvalidOperationException(String.Format("cannot retrieve property {0} of NULL", name));
            var prop = target.getPropertyInfo(name);
            if (null == prop)
                throw new InvalidOperationException(String.Format("No property {0} on class {1}", name, target.GetType()));
            return target.getPropertyInfo(name).GetValue(target, null);
        }

        public static T setProperty<T>(this T target, string name, object value)
        {
            if (null == target) throw new InvalidOperationException();
            var pi = target.getPropertyInfo(name);
            if (null == pi)
                throw new InvalidOperationException(String.Format("No property {0} on class {1}", name, target.GetType()));
            object newValue = value;
            var propertyType = pi.PropertyType;
            if (null == newValue || !propertyType.IsAssignableFrom(newValue.GetType()))
            {
                if (propertyType.IsValueType)
                {
                    if (null == newValue)
                    {
                        newValue = Convert.ChangeType(0, propertyType);
                    }
                }

            }

            if (null != newValue)
            {
                if (propertyType.IsEnum)
                {
                    newValue = Enum.Parse(propertyType, newValue.ToString());
                }
                else
                {
                    if (propertyType.IsValueType)
                    {
                        newValue = Convert.ChangeType(newValue, propertyType);
                    }
                }
            }

            target.getPropertyInfo(name).SetValue(target, newValue, null);
            return target;
        }

        public static IEnumerable<Type> getTypesWithAttribute<T>(this T attribute)
            where T : Attribute
        {
            return getTypesWithAttribute(attribute, (IEnumerable<Assembly>)null);
        }

        public static IEnumerable<Type> getTypesWithAttribute<T>(this T attribute, IEnumerable<Assembly> assemblies)
            where T : Attribute
        {
            return getTypesWithAttribute(attribute, assemblies, null);
        }

        public static IEnumerable<Type> getTypesWithAttribute<T>(this T attribute, Func<T, Type, bool> filter)
            where T : Attribute
        {
            return getTypesWithAttribute(attribute, null, filter);
        }

        public static IEnumerable<Type> getTypesWithAttribute<T>(this T attribute, IEnumerable<Assembly> assemblies,
                                                                 Func<T, Type, bool> filter)
            where T : Attribute
        {
            var attributeType = typeof(T);
            var assemblyset = assemblies.yes() ? assemblies : AppDomain.CurrentDomain.GetAssemblies();
            var filterFunc = filter ?? ((a, t) => true);

            foreach (var assembly in assemblyset)
            {
                var types = assembly.GetTypes();
                if (types.yes())
                {
                    foreach (var type in types)
                    {
                        var attributes = type.GetCustomAttributes(attributeType, true);
                        if (attributes.no()) continue;
                        var filtered = false;
                        foreach (T attr in attributes)
                        {
                            if (filterFunc(attr, type))
                            {
                                filtered = true;
                                break;
                            }
                        }
                        if (filtered)
                            yield return type;
                    }
                }
            }
        }

        public static IEnumerable<Type> getTypesWithAttribute(this Assembly assembly, Type attributeType)
        {
            foreach (var type in assembly.GetTypes())
                if (type.GetCustomAttributes(attributeType, true).yes()) yield return type;
        }

        public static IEnumerable<Type> getTypesWithAttribute<T>(this Assembly assembly) where T : Attribute
        {
            return assembly.getTypesWithAttribute(typeof(T));
        }

        public static IEnumerable<MemberInfo> getMembersWithAttribute<T>(this T attribute, Type targetType,
                                                                         Func<T, MemberInfo, bool> filter)
            where T : Attribute
        {
            return getMembersWithAttribute(attribute, targetType, BindingFlags.Default, filter);
        }

        public static IEnumerable<MemberInfo> getMembersWithAttribute<T>(this T attribute, Type targetType,
                                                                         BindingFlags binding)
            where T : Attribute
        {
            return getMembersWithAttribute(attribute, targetType, binding, null);
        }

        public static IEnumerable<MemberInfo> getMembersWithAttribute(this object source, Type attributeType)
        {
            var type = (source is Type) ? (Type)source : source.GetType();
            foreach (var memberInfo in type.GetMembers())
                if (memberInfo.GetCustomAttributes(attributeType, true).yes()) yield return memberInfo;
        }

        public static IEnumerable<MemberInfo> getMembersWithAttribute<T>(this T attribute, Type targetType)
            where T : Attribute
        {
            return getMembersWithAttribute(attribute, targetType, BindingFlags.Default, null);
        }

        public static IEnumerable<MemberInfo> getMembersWithAttribute<T>(this T attribute, Type targetType,
                                                                         BindingFlags binding,
                                                                         Func<T, MemberInfo, bool> filter)
            where T : Attribute
        {
            if (null == targetType) throw new ArgumentNullException("targetType");

            var attributeType = typeof(T);
            var filterFunc = filter ?? ((a, t) => true);

            var members = binding == BindingFlags.Default ? targetType.GetMembers() : targetType.GetMembers(binding);

            foreach (var member in members)
            {
                var attributes = member.GetCustomAttributes(attributeType, true);
                if (attributes.no()) continue;
                var filtered = false;
                foreach (T attr in attributes)
                {
                    if (filterFunc(attr, member))
                    {
                        filtered = true;
                        break;
                    }
                }
                if (filtered)
                    yield return member;
            }
        }

        public static Type[] toTypes(this object[] parameters)
        {
            return Enumerable.Select(parameters, p => null == p ? typeof(object) : p.GetType()).ToArray();
        }


        public static bool hasProperty(this CodeTypeDeclaration typeDef, string property)
        {
            for (int i = 0; i < typeDef.Members.Count; i++)
            {
                if (typeDef.Members[i] is CodeMemberProperty && typeDef.Members[i].Name == property)
                {
                    return true;
                }
            }
            return false;
        }
        public static bool hasMethod(this CodeTypeDeclaration typeDef, string method)
        {
            for (int i = 0; i < typeDef.Members.Count; i++)
            {
                if (typeDef.Members[i] is CodeMemberMethod && typeDef.Members[i].Name == method)
                {
                    return true;
                }
            }
            return false;
        }
    }
}